/**
 * Copyright (c) 2012-2016, www.tinygroup.org (luo_guo@icloud.com).
 * <p>
 * Licensed under the GPL, Version 3.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.gnu.org/licenses/gpl.html
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.tinygroup.fileresolver;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.tinygroup.commons.tools.StringUtil;
import org.tinygroup.config.util.ConfigurationUtil;
import org.tinygroup.logger.LogLevel;
import org.tinygroup.logger.Logger;
import org.tinygroup.logger.LoggerFactory;
import org.tinygroup.vfs.FileObject;
import org.tinygroup.vfs.FileObjectFilter;
import org.tinygroup.vfs.FileObjectProcessor;
import org.tinygroup.vfs.VFS;
import org.tinygroup.vfs.impl.JarSchemaProvider;

public class FileResolverUtil {
	private static final Logger LOGGER = LoggerFactory.getLogger(FileResolverUtil.class);
	private static String tempFolderPath = null;
	private static File tempFolder = null;

	private FileResolverUtil() {

	}

	public static void addClassPathPattern(FileResolver resolver) {
		resolver.addIncludePathPattern("[\\/]classes\\b");
		resolver.addIncludePathPattern("[\\/]test-classes\\b");
	}

//	public static void addSpringBootPath(FileResolver resolver) throws Exception {
//	}

	public static void addSpringBootPath1(FileResolver resolver) throws Exception {
		if (tempFolderPath != null) {
			resolver.addResolvePath(tempFolderPath);
		}
		JarFile jarFile = null;
		ClassLoader loader = Thread.currentThread().getContextClassLoader();
		URL url = loader.getResource("BOOT-INF");

		if (url == null) {
			return;
		}
		String jarFilePath = url.toString();
		LOGGER.logMessage(LogLevel.INFO, "SpringBoot BOOT-INF:{}", jarFilePath);
		jarFilePath = jarFilePath.replace("jar:", "").replace("file:", ""); // 移除协议头
		int jarIndex = jarFilePath.indexOf(".jar");
		jarFilePath = jarFilePath.substring(0, jarIndex + 4);
		jarFile = new JarFile(jarFilePath);
		LOGGER.logMessage(LogLevel.INFO, "SpringBoot JAR:{}", jarFilePath);
		Enumeration<JarEntry> enties = jarFile.entries();
		List<JarEntry> jars = new ArrayList<JarEntry>();
		while (enties.hasMoreElements()) {
			JarEntry j = enties.nextElement();
			if (j.toString().endsWith(".jar")) {
				jars.add(j);
			}

		}
		File folder = getTempUnpackFolder(jarFile);

		for (JarEntry jar : jars) {
			String name = jar.getName();
			if (name.lastIndexOf("/") != -1) { // 这里不能改成File.separator
				name = name.substring(name.lastIndexOf("/") + 1);
			}
			File file = new File(folder, name);
			unpack(jarFile, file, jar);
		}
		tempFolderPath = folder.getPath();
		resolver.addResolvePath(tempFolderPath);
	}

	private static void unpack(JarFile jarFile, File file, JarEntry entry) throws IOException {
		InputStream inputStream = jarFile.getInputStream(entry);
		try {
			OutputStream outputStream = new FileOutputStream(file);
			try {
				byte[] buffer = new byte[32768];
				int bytesRead = -1;
				while ((bytesRead = inputStream.read(buffer)) != -1) {
					outputStream.write(buffer, 0, bytesRead);
				}
				outputStream.flush();
			} finally {
				outputStream.close();
			}
		} finally {
			inputStream.close();
		}
	}

	private static File getTempUnpackFolder(JarFile jarFile) {
		File tempFolder = new File(System.getProperty("java.io.tmpdir"));

		return createUnpackFolder(jarFile, tempFolder);

	}

	private static File createUnpackFolder(JarFile jarFile, File temp) {
		int attempts = 0;
		while (attempts++ < 1000) {
			String fileName = new File(jarFile.getName()).getName();

			File unpackFolder = new File(temp, fileName + "-tiny-libs-" + UUID.randomUUID());
			if (unpackFolder.mkdirs()) {
				return unpackFolder;
			}
		}
		throw new IllegalStateException("Failed to create unpack folder in directory '" + temp + "'");
	}

	public static List<String> getClassPath(FileResolver resolver) {
		List<String> classPathFileObjects = new ArrayList<String>();
		String classPathProperty = System.getProperty("java.class.path").toString();
		String operateSys = System.getProperty("os.name").toLowerCase();
		String[] classPaths = null;
		if (operateSys.indexOf("windows") >= 0) {
			classPaths = classPathProperty.split(";");
		} else {
			classPaths = classPathProperty.split(":");
		}
		if (classPaths == null) {
			return classPathFileObjects;
		}
		// if (classPaths != null) {
		for (String classPath : classPaths) {
			if (classPath.length() > 0) {
				FileObject fileObject = VFS.resolveFile(classPath);
				if (isInclude(fileObject, resolver)) {
					classPathFileObjects.add(fileObject.getAbsolutePath());
				}
			}
		}
		// }
		return classPathFileObjects;
	}

	public static boolean isInclude(FileObject fileObject, FileResolver resolver) {
		// jboss可能出现解析到根本不存在的jar包，这种情况直接忽略
		if (fileObject == null || !fileObject.isExist()) {
			return false;
		}
		URL url = fileObject.getURL();
		if (("file").equals(url.getProtocol()) && fileObject.getSchemaProvider() instanceof JarSchemaProvider) {
			Map<String, Pattern> includePathPatternMap = resolver.getIncludePathPatternMap();
			for (String patternString : includePathPatternMap.keySet()) {
				Pattern pattern = includePathPatternMap.get(patternString);
				Matcher matcher = pattern.matcher(fileObject.getFileName());
				if (matcher.find()) {
					// logger.logMessage(LogLevel.INFO,
					// "文件<{}>由于匹配了包含正则表达式<{}>而被扫描。", fileObject,
					// patternString);
					return true;
				}
			}
			return false;
		} else {
			return true;
		}
	}

	public static String dealJarSource(FileResolver resolver, String path) {
		if (path.indexOf("!") < 0) {
			path = path.substring(0, path.length() - "META-INF/MANIFEST.MF".length() - 1);
			path = path.substring(path.indexOf(':') + 1);
			return path;
		}
		String[] pathArray = path.split("!");
		if (pathArray.length <= 2) {
			return pathArray[0];
		}
		LOGGER.infoMessage("存在jar包嵌套jar包的路径：{}",path);
		File tempFolder = createTempUnpackFolder();
		String bastPath = pathArray[0].replace("jar:", "").replace("file:", "");
		for (int i = 1; i < pathArray.length - 1; i++) {
			try {
				bastPath = unpackFileToTempFolder(tempFolder, bastPath, pathArray[i]);
			} catch (IOException e) {
				LOGGER.errorMessage("解压jar包嵌套资源发生IO异常,{},{}",e,bastPath,pathArray[i]);
			}
		}

		return bastPath;
	}

	private static File createTempUnpackFolder() {
		if (tempFolder != null) {
			return tempFolder;
		}
		File tempFolder = new File(System.getProperty("java.io.tmpdir"));
		int attempts = 0;
		while (attempts++ < 1000) {
			File unpackFolder = new File(tempFolder, "tiny-app-libs" + UUID.randomUUID());
			if (unpackFolder.mkdirs()) {
				return unpackFolder;
			}
		}
		throw new IllegalStateException("Failed to create unpack folder in directory '" + tempFolder + "'");
	}

	public static String unpackFileToTempFolder(File tempFolder, String path1, String path2) throws IOException {
		if(path2.startsWith("/")){
			path2=path2.substring(1);
		}
		//因为要作为文件夹名，所以去掉.jar
		String name1 = computeFileName(path1, true);
		//作为文件名，所以不用去掉.jar
		String name2 = computeFileName(path2, false);
		//拿到父jar
		JarFile jar1File = new JarFile(path1);
		//父jar目录
		File folder1 = new File(tempFolder, name1);
		if(!folder1.exists()){
			folder1.mkdirs();
		}
		//子jar文件
		File jar2File = new File(folder1, name2);
		//从父jar中读取子jar，写入子jar文件
		unpack(jar1File, jar2File, jar1File.getJarEntry(path2));
		return jar2File.getPath();
	}

	private static String computeFileName(String path, boolean dealJar) {
		String name = path; // a
		if (name.lastIndexOf("/") != -1) { // 这里不能改成File.separator
			name = name.substring(name.lastIndexOf("/") + 1);
		}
		if (dealJar && name.endsWith(".jar")) {
			name = name.substring(0, name.length() - 4);
		}
		return name;
	}

	public static List<String> getWebLibJars(FileResolver resolver) throws Exception {
		final List<String> classPaths = new ArrayList<String>();
		LOGGER.logMessage(LogLevel.INFO, "查找Web工程中的jar文件列表开始...");
		ClassLoader loader = Thread.currentThread().getContextClassLoader();
		Enumeration<URL> urls = loader.getResources("META-INF/MANIFEST.MF");
		Map<String, Pattern> includePathPatternMap = resolver.getIncludePathPatternMap();
		while (urls.hasMoreElements()) {
			URL url = urls.nextElement();
			String path = url.toString();
			// 20170614
			// 修改path.replaceAll("/./", "/")为path.replaceAll("/\\./", "/")
			// 解决路径中存在单字符时，会被replace掉的问题，参见测试用例TestReplaceAll
			path = path.replaceAll("/\\./", "/");// weblogic中存在这种情况
			// path = path.replaceAll("/./", "/");// weblogic中存在这种情况

			//20170720 注释下面7行，将其放入dealJarSource中并进行功能扩展
			// if (path.indexOf("!") > 0) {
			// path = path.split("!")[0];
			// } else {// 专门为JBOSS vfs开头的处理
			// path = path.substring(0, path.length() -
			// "META-INF/MANIFEST.MF".length() - 1);
			// path = path.substring(path.indexOf(':') + 1);
			// }
			path = dealJarSource(resolver, path);
			FileObject fileObject = VFS.resolveFile(path);
			if (includePathPatternMap != null && !includePathPatternMap.isEmpty()) {
				if (isInclude(fileObject, resolver)) {
					addJarFile(classPaths, fileObject.getAbsolutePath());
					continue;
				}
			}
			if (fileObject.isExist()) {
				InputStream stream = null;
				try {
					stream = url.openStream();
					Manifest mf = new Manifest(stream);
					Attributes attributes = mf.getMainAttributes();
					String isTinyProject = attributes.getValue("IsTinyProject");
					if ("true".equals(isTinyProject)) {
						LOGGER.logMessage(LogLevel.INFO, "文件<{}>由于在MANIFEST.MF文件中声明了IsTinyProject: true而被扫描。",
								fileObject);
						addJarFile(classPaths, fileObject.getAbsolutePath());
					}
				} finally {
					if (stream != null) {
						stream.close();
					}
				}
			}

		}
		// Jboss新版本会将war资源解压到临时目录，需要根据TINY_WEBROOT再查找
		String webinfPath = ConfigurationUtil.getConfigurationManager().getConfiguration().get("TINY_WEBROOT");
		if (!StringUtil.isEmpty(webinfPath)) {
			FileObject webroot = VFS.resolveFile(webinfPath);
			webroot.foreach(new FileObjectFilter() {
				public boolean accept(FileObject fileObject) {
					return fileObject.getFileName().endsWith(".jar");
				}
			}, new FileObjectProcessor() {

				public void process(FileObject fileObject) {
					FileObject mfObject = fileObject.getFileObject("META-INF/MANIFEST.MF");
					if (mfObject == null || !mfObject.isExist()) {
						return;
					}
					InputStream inputStream = null;
					try {
						inputStream = mfObject.getInputStream();
						Manifest mf = new Manifest(inputStream);
						Attributes attributes = mf.getMainAttributes();
						String isTinyProject = attributes.getValue("IsTinyProject");
						if ("true".equals(isTinyProject)) {
							LOGGER.logMessage(LogLevel.INFO, "文件<{}>由于在MANIFEST.MF文件中声明了IsTinyProject: true而被扫描。",
									fileObject);
							addJarFile(classPaths, fileObject.getAbsolutePath());
						}
					} catch (IOException e) {
						LOGGER.logMessage(LogLevel.WARN, "解析MANIFEST.MF发生异常:{}", mfObject.getAbsolutePath());
					} finally {
						if (inputStream != null) {
							try {
								inputStream.close();
							} catch (IOException e) {
								// do nothing
							}
						}
					}

				}
			});
		}

		LOGGER.logMessage(LogLevel.INFO, "查找Web工程中的jar文件列表完成。");
		return classPaths;
	}

	private static void addJarFile(List<String> classPaths, String path) {
		LOGGER.logMessage(LogLevel.INFO, "扫描到jar文件<{}>。", path);
		classPaths.add(path);
	}

	public static List<String> getWebClasses() {
		List<String> allScanningPath = new ArrayList<String>();
		LOGGER.logMessage(LogLevel.INFO, "查找WEB-INF/classes路径开始...");
		URL url = getURL();
		String path = url.toString();
		LOGGER.logMessage(LogLevel.INFO, "WEB-INF/classes路径是:{}", path);
		if (path.indexOf("!") < 0) {// 如果在目录中
			FileObject fileObject = VFS.resolveFile(path);
			allScanningPath.add(fileObject.getAbsolutePath());
			String libPath = path.replaceAll("/classes", "/lib");
			LOGGER.logMessage(LogLevel.INFO, "WEB-INF/lib路径是:{}", libPath);
			FileObject libFileObject = VFS.resolveFile(libPath);

			allScanningPath.add(libFileObject.getAbsolutePath());
			int index = path.indexOf("/classes");
			if (index > 0) {
				String webInfPath = path.substring(0, index);
				if (webInfPath.endsWith("WEB-INF")) {
					LOGGER.logMessage(LogLevel.INFO, "WEB-INF路径是:{}", webInfPath);
					FileObject webInfoFileObject = VFS.resolveFile(webInfPath);
					allScanningPath.add(webInfoFileObject.getAbsolutePath());
				}
			}

		} else {// 如果在jar包中
			path = url.getFile().split("!")[0];
			FileObject fileObject = VFS.resolveFile(path);
			allScanningPath.add(fileObject.getAbsolutePath());
			String libPath = path.substring(0, path.lastIndexOf('/'));
			LOGGER.logMessage(LogLevel.INFO, "WEB-INF/lib路径是:{}", libPath);
			FileObject libFileObject = VFS.resolveFile(libPath);
			allScanningPath.add(libFileObject.getAbsolutePath());
		}
		LOGGER.logMessage(LogLevel.INFO, "查找WEB-INF/classes路径完成。");

		String webinfPath = ConfigurationUtil.getConfigurationManager().getConfiguration().get("TINY_WEBROOT");
		if (StringUtil.isEmpty(webinfPath)) {
			LOGGER.logMessage(LogLevel.WARN, "WEBROOT变量找不到");
			return allScanningPath;
		}
		FileObject fileObject = VFS.resolveFile(webinfPath);
		allScanningPath.add(fileObject.getAbsolutePath());
		return allScanningPath;
	}

	private static URL getURL() {
		URL url = null;
		String weblogicMode = ConfigurationUtil.getConfigurationManager().getConfiguration("WEBLOGIC_MODE");
		if (weblogicMode != null && "true".equalsIgnoreCase(weblogicMode)) {
			url = FileResolverUtil.class.getClassLoader().getResource("/");
			if (url == null) {
				url = FileResolverUtil.class.getClassLoader().getResource("");
			}
		} else {
			url = FileResolverUtil.class.getResource("/");
			if (url == null) {
				url = FileResolverUtil.class.getResource("");
			}
		}
		return url;
	}
}
